#include "Endian.h"
#include "Macros.h"
#include <boost/cstdint.hpp>

#ifdef _MSC_VER
#include <intrin.h>
#endif

using namespace boost;

namespace lhcutils
{

Endianness::Value GetHostEndianness_()
{
	union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

	if(bint.c[0] == 1)
	{
		return Endianness::Big;
	}
	else
	{
		return Endianness::Little;
	}
}

Endianness::Value GetHostEndianness()
{
#ifdef FORCE_ENDIANNESS_BIG
	return Endianness::Big;
#elif defined FORCE_ENDIANNESS_LITTLE
	return Endianness::Little;
#else
	static Endianness::Value hostEndianness = GetHostEndianness_();
	return hostEndianness;
#endif
}

// Force the static hostEndianness in GetHostEndianness() to be initialized on startup.
// This is to prevent threading issues caused by multiple threads attempting to initialize a static at the same time.
// Do not actually use this variable.
UNUSED Endianness::Value g_hostEndianness_ = GetHostEndianness();

uint16_t FlipBytes16(uint16_t int16)
{
#if defined _MSC_VER && !defined LHC_FORCE_SLOW_BYTE_SWAP
	return _byteswap_ushort(int16);
#else
	uint8_t* inputPtr = reinterpret_cast<uint8_t*>(&int16);

	uint16_t result;
	uint8_t* resultPtr = reinterpret_cast<uint8_t*>(&result);
	resultPtr[0] = inputPtr[1];
	resultPtr[1] = inputPtr[0];

	return result;
#endif
}

uint32_t FlipBytes32(uint32_t int32)
{
#if defined _MSC_VER && !defined LHC_FORCE_SLOW_BYTE_SWAP
	return _byteswap_ulong(int32);
#elif defined __GNUC__ && !defined LHC_FORCE_SLOW_BYTE_SWAP
	return __builtin_bswap32(int32);
#else
	uint8_t* inputPtr = reinterpret_cast<uint8_t*>(&int32);
	
	uint32_t result;
	uint8_t* resultPtr = reinterpret_cast<uint8_t*>(&result);
	resultPtr[0] = inputPtr[3];
	resultPtr[1] = inputPtr[2];
	resultPtr[2] = inputPtr[1];
	resultPtr[3] = inputPtr[0];

	return result;
#endif
}

uint64_t FlipBytes64(uint64_t int64)
{
#if defined _MSC_VER && !defined LHC_FORCE_SLOW_BYTE_SWAP
	return _byteswap_uint64(int64);
#elif defined __GNUC__ && !defined LHC_FORCE_SLOW_BYTE_SWAP
	return __builtin_bswap64(int32);
#else
	uint8_t* inputPtr = reinterpret_cast<uint8_t*>(&int64);

	uint64_t result;
	uint8_t* resultPtr = reinterpret_cast<uint8_t*>(&result);
	resultPtr[0] = inputPtr[7];
	resultPtr[1] = inputPtr[6];
	resultPtr[2] = inputPtr[5];
	resultPtr[3] = inputPtr[4];
	resultPtr[4] = inputPtr[3];
	resultPtr[5] = inputPtr[2];
	resultPtr[6] = inputPtr[1];
	resultPtr[7] = inputPtr[0];

	return result;
#endif
}

uint16_t LeToHost16(uint16_t int16)
{
	if(GetHostEndianness() == Endianness::Little)
	{
		return int16;
	}
	else
	{
		return FlipBytes16(int16);
	}
}

uint16_t BeToHost16(uint16_t int16)
{
	if(GetHostEndianness() == Endianness::Big)
	{
		return int16;
	}
	else
	{
		return FlipBytes16(int16);
	}
}

uint32_t LeToHost32(uint32_t int32)
{
	if(GetHostEndianness() == Endianness::Little)
	{
		return int32;
	}
	else
	{
		return FlipBytes32(int32);
	}
}

uint32_t BeToHost32(uint32_t int32)
{
	if(GetHostEndianness() == Endianness::Big)
	{
		return int32;
	}
	else
	{
		return FlipBytes32(int32);
	}
}

uint64_t LeToHost64(uint64_t int64)
{
	if(GetHostEndianness() == Endianness::Little)
	{
		return int64;
	}
	else
	{
		return FlipBytes64(int64);
	}
}

uint64_t BeToHost64(uint64_t int64)
{
	if(GetHostEndianness() == Endianness::Big)
	{
		return int64;
	}
	else
	{
		return FlipBytes64(int64);
	}
}

uint16_t ToHost16(uint16_t int16, Endianness::Value endianness)
{
	if(GetHostEndianness() == endianness)
	{
		return int16;
	}
	else
	{
		return FlipBytes16(int16);
	}
}

uint32_t ToHost32(uint32_t int32, Endianness::Value endianness)
{
	if(GetHostEndianness() == endianness)
	{
		return int32;
	}
	else
	{
		return FlipBytes32(int32);
	}
}

uint64_t ToHost64(uint64_t int64, Endianness::Value endianness)
{
	if(GetHostEndianness() == endianness)
	{
		return int64;
	}
	else
	{
		return FlipBytes64(int64);
	}
}

uint16_t HostTo16(uint16_t int16, Endianness::Value endianness)
{
	if(GetHostEndianness() == endianness)
	{
		return int16;
	}
	else
	{
		return FlipBytes16(int16);
	}
}

uint16_t HostToLe16(uint16_t int16)
{
	return HostTo16(int16, Endianness::Little);
}

uint16_t HostToBe16(uint16_t int16)
{
	return HostTo16(int16, Endianness::Big);
}

uint32_t HostTo32(uint32_t int32, Endianness::Value endianness)
{
	if(GetHostEndianness() == endianness)
	{
		return int32;
	}
	else
	{
		return FlipBytes32(int32);
	}
}

uint32_t HostToLe32(uint32_t int32)
{
	return HostTo32(int32, Endianness::Little);
}

uint32_t HostToBe32(uint32_t int32)
{
	return HostTo32(int32, Endianness::Big);
}

uint64_t HostTo64(uint64_t int64, Endianness::Value endianness)
{
	if(GetHostEndianness() == endianness)
	{
		return int64;
	}
	else
	{
		return FlipBytes64(int64);
	}
}

uint64_t HostToLe64(uint64_t int64)
{
	return HostTo64(int64, Endianness::Little);
}

uint64_t HostToBe64(uint64_t int64)
{
	return HostTo64(int64, Endianness::Big);
}

}

/*
 Copyright 2011 Greg Najda

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
